<?php

namespace ip;
use \Exception;
class Searcher
{
    const HeaderInfoLength = 256;
    const VectorIndexRows  = 256;
    const VectorIndexCols  = 256;
    const VectorIndexSize  = 8;
    const SegmentIndexSize = 14;

    // 文件句柄
    private $handle = null;

    // 头信息
    private $header = null;
    private $ioCount = 0;

    // 二进制字符串中的矢量索引。
    //字符串解码将比基于映射的Array更快。
    private $vectorIndex = null;

    // 内容缓冲区
    private $contentBuff = null;

   
    /**
     * 初始化搜索器
     * @throws Exception
     */
    function __construct() {
        if ($this->handle== null) {
            $dbFile=dirname(__FILE__) ."/ip.dat";
            $this->handle = fopen($dbFile, "r");
            if ($this->handle === false) {
              
               throw new Exception("failed to open dat file '%s'", $dbFile);
            }
        }
    }

    function close() {
        if ($this->handle != null) {
            fclose($this->handle);
        }
    }

    function getIOCount() {
        return $this->ioCount;
    }

    /**
     * 查找指定ip地址的区域信息
     * @throws Exception
     */
    function search($ip) {
        // 检查并将sting ip转换为4字节长
        if (is_string($ip)) {
            $t = self::ip2long($ip);
            if ($t === null) {
                throw new Exception("invalid ip address `$ip`");
            }
            $ip = $t;
        }

        // 重置全局计数器
        $this->ioCount = 0;

        // 基于矢量索引定位分段索引块

        $il0 = ($ip >> 24) & 0xFF;
        $il1 = ($ip >> 16) & 0xFF;
        $idx = $il0 * self::VectorIndexCols * self::VectorIndexSize + $il1 * self::VectorIndexSize;
        if ($this->vectorIndex != null) {
            $sPtr = self::getLong($this->vectorIndex, $idx);
            $ePtr = self::getLong($this->vectorIndex, $idx + 4);
        } else if ($this->contentBuff != null) {
            $sPtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx);
            $ePtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx + 4);
        } else {
            // 读取矢量索引块
            $buff = $this->read(self::HeaderInfoLength + $idx, 8);
            if ($buff === null) {
                throw new Exception("failed to read vector index at ${idx}");
            }

            $sPtr = self::getLong($buff, 0);
            $ePtr = self::getLong($buff, 4);
        }


        // 二进制搜索片段索引以获取区域信息
        $dataLen = 0;
        $dataPtr = null;
        $l = 0;
        $h = ($ePtr - $sPtr) / self::SegmentIndexSize;
        while ($l <= $h) {
            $m = ($l + $h) >> 1;
            $p = $sPtr + $m * self::SegmentIndexSize;

            // 读取段索引
            $buff = $this->read($p, self::SegmentIndexSize);
            if ($buff == null) {
                throw new Exception("failed to read segment index at ${p}");
            }

            $sip = self::getLong($buff, 0);
            if ($ip < $sip) {
                $h = $m - 1;
            } else {
                $eip = self::getLong($buff, 4);
                if ($ip > $eip) {
                    $l = $m + 1;
                } else {
                    $dataLen = self::getShort($buff, 8);
                    $dataPtr = self::getLong($buff, 10);
                    break;
                }
            }
        }

       //不匹配的拦截
        if ($dataPtr == null) {
            return null;
        }

        // 加载并返回区域数据
        $buff = $this->read($dataPtr, $dataLen);
        if ($buff == null) {
            return null;
        }

        return $buff;
    }

    // 从指定的索引中读取指定的字节
    private function read($offset, $len) {
        // 先检查内存中的缓冲区
        if ($this->contentBuff != null) {
            return substr($this->contentBuff, $offset, $len);
        }

        // 从文件中读取
        $r = fseek($this->handle, $offset);
        if ($r == -1) {
            return null;
        }

        $this->ioCount++;
        $buff = fread($this->handle, $len);
        if ($buff === false) {
            return null;
        }

        if (strlen($buff) != $len) {
            return null;
        }

        return $buff;
    }

   
    // 将字符串ip转换为long
    public static function ip2long($ip)
    {
        $ip = ip2long($ip);
        if ($ip === false) {
            return null;
        }

        // 在32位操作系统上将带符号的int转换为无符号的int
        if ($ip < 0 && PHP_INT_SIZE == 4) {
            $ip = sprintf("%u", $ip);
        }

        return $ip;
    }

    // 从字节缓冲区读取4字节长
    public static function getLong($b, $idx)
    {
        $val = (ord($b[$idx])) | (ord($b[$idx+1]) << 8)
            | (ord($b[$idx+2]) << 16) | (ord($b[$idx+3]) << 24);

        // 在32位操作系统上将带符号的int转换为无符号的int
        if ($val < 0 && PHP_INT_SIZE == 4) {
            $val = sprintf("%u", $val);
        }

        return $val;
    }

    // 从字节缓冲区中读取2字节的短
    public static function getShort($b, $idx)
    {
        return ((ord($b[$idx])) | (ord($b[$idx+1]) << 8));
    }

    // 从指定的文件句柄加载标头信息
    public  function loadHeader() {
        $handle=$this->handle;
        if (fseek($handle, 0) == -1) {
            return null;
        }

        $buff = fread($handle, self::HeaderInfoLength);
        if ($buff === false) {
            return null;
        }

        // 读取字节长度检查
        if (strlen($buff) != self::HeaderInfoLength) {
            return null;
        }

        // 返回解码后的标头信息
        return array(
            'version'       => self::getShort($buff, 0),
            'indexPolicy'   => self::getShort($buff, 2),
            'createdAt'     => self::getLong($buff, 4),
            'startIndexPtr' => self::getLong($buff, 8),
            'endIndexPtr'   => self::getLong($buff, 12)
        );
    }

  

  

  
}
